/*
 * Copyright 2020 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.kie.dmn.typesafe;

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import org.drools.model.codegen.execmodel.generator.declaredtype.api.TypeDefinition;
import org.drools.model.codegen.execmodel.generator.declaredtype.generator.GeneratedClassDeclaration;
import org.kie.dmn.api.core.DMNModel;
import org.kie.dmn.api.core.DMNType;
import org.kie.dmn.api.core.ast.BusinessKnowledgeModelNode;
import org.kie.dmn.api.core.ast.DecisionNode;
import org.kie.dmn.api.core.ast.DecisionServiceNode;
import org.kie.dmn.api.core.ast.InputDataNode;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DMNTypeSafeTypeGenerator {

    private static final Logger LOG = LoggerFactory.getLogger(DMNTypeSafeTypeGenerator.class);

    private final DMNTypeSafePackageName.Factory packageName;
    private DMNAllTypesIndex index;
    private DMNModelImpl dmnModel;
    private final String disclaimerMarker;

    private DMNStronglyCodeGenConfig codeGenConfig = new DMNStronglyCodeGenConfig();

    private List<TypeDefinition> types = new ArrayList<>();

    private static final String JAVADOC_BRNL = "<br/>\n";

    public DMNTypeSafeTypeGenerator(DMNModel dmnModel,
                                    DMNAllTypesIndex index,
                                    DMNTypeSafePackageName.Factory packageName) {
        this.dmnModel = (DMNModelImpl) dmnModel;
        this.packageName = packageName;
        this.index = index;
        this.disclaimerMarker = disclaimerMarker();
    }

    // So far it's used in Kogito DMN
    public DMNTypeSafeTypeGenerator withJacksonAnnotation() {
        codeGenConfig.setWithJacksonAnnotation(true);
        return this;
    }

    // So far it's used in Kogito DMN
    public DMNTypeSafeTypeGenerator withMPAnnotation() {
        codeGenConfig.setWithMPOpenApiAnnotation(true);
        return this;
    }

    // So far it's used in Kogito DMN
    public DMNTypeSafeTypeGenerator withIOSwaggerOASv3() {
        codeGenConfig.setWithIOSwaggerOASv3Annotation(true);
        return this;
    }

    private static String disclaimerMarker() {
        StringBuilder sb = new StringBuilder("").append("\n");
        sb.append("This file was automatically generated by the Drools DMN open source engine Strongly Typed facility.").append("\n");
        sb.append("Do not modify this file, as any modifications will be lost when invoking the facility again.").append("\n");
        sb.append("Do not rely on the generated class(es) implementation, as the actual implementations may change.").append("\n");
        sb.append("Generated on: " + ZonedDateTime.now().toString()).append("\n");
        sb.append("\n");
        return sb.toString();
    }

    private String postfixToJavadoc(String prefix, DMNModelImpl dmnModel) {
        StringBuilder sb = new StringBuilder(prefix).append(JAVADOC_BRNL);
        sb.append(JAVADOC_BRNL);
        sb.append("This has been automatically generated from the following DMN asset.").append(JAVADOC_BRNL);
        sb.append("DMN namespace: ").append(dmnModel.getNamespace()).append(JAVADOC_BRNL);
        sb.append("DMN name: ").append(dmnModel.getName()).append(JAVADOC_BRNL);
        sb.append("\n");
        sb.append("@implNote ").append(disclaimerMarker).append(JAVADOC_BRNL);
        return sb.toString();
    }

    public DMNTypeSafeTypeGenerator processTypes() {
        Set<InputDataNode> inputs = dmnModel.getInputs();
        DMNInputSetType inputSetType = new DMNInputSetType(index, codeGenConfig);
        for (InputDataNode i : inputs) {
            inputSetType.addField(i.getName(), i.getType());
        }
        inputSetType.initFields();
        inputSetType.setJavadoc(postfixToJavadoc(new StringBuilder("A representation of all the InputData and other DRG Requirement of the whole DMN '").append(dmnModel.getName()).append("' inputs.").toString(), dmnModel));

        types.add(inputSetType);

        Set<DecisionNode> decisions = dmnModel.getDecisions();
        Collection<DecisionServiceNode> decisionServices = dmnModel.getDecisionServices();
        Set<BusinessKnowledgeModelNode> bkms = dmnModel.getBusinessKnowledgeModels();

        DMNOutputSetType outputSetType = new DMNOutputSetType(index, codeGenConfig);
        for (InputDataNode i : inputs) {
            outputSetType.addField(i.getName(), i.getType()); // OutputSet also contains inputs
        }
        for (DecisionNode d : decisions) {
            outputSetType.addField(d.getName(), d.getResultType());
        }
        for (DecisionServiceNode ds : decisionServices) {
            outputSetType.addField(ds.getName(), dmnModel.getTypeRegistry().unknown());
        }
        for (BusinessKnowledgeModelNode bkm : bkms) {
            outputSetType.addField(bkm.getName(), dmnModel.getTypeRegistry().unknown());
        }

        outputSetType.initFields();
        outputSetType.setJavadoc(postfixToJavadoc(new StringBuilder("A representation of all the OutputData of the whole DMN '").append(dmnModel.getName()).append("' outputs.").toString(), dmnModel));

        types.add(outputSetType);

        for (DMNType type : index.typesToGenerateByNS(dmnModel.getNamespace())) { // this generator shall only be concerned with the types belonging to this generator dmnModel.
            DMNDeclaredType dmnDeclaredType = new DMNDeclaredType(index, type, codeGenConfig);
            dmnDeclaredType.setJavadoc(postfixToJavadoc(new StringBuilder("A representation of the DMN defined ItemDefinition type '").append(type.getName()).append("'.").toString(), dmnModel));
            types.add(dmnDeclaredType);
        }

        return this;
    }

    public Map<String, String> generateSourceCodeOfAllTypes() {
        Map<String, String> allSources = new HashMap<>();
        DMNTypeSafePackageName packageDeclaration = this.packageName.create(dmnModel);
        for (TypeDefinition typeDefinition : types) {
            ClassOrInterfaceDeclaration generatedClass = new GeneratedClassDeclaration(typeDefinition,
                                                                                       Collections.emptyList()).toClassDeclaration();

            CompilationUnit cu = new CompilationUnit(packageDeclaration.packageName());
            cu.getPackageDeclaration().ifPresent(pd -> pd.setBlockComment(this.disclaimerMarker));
            cu.addType(generatedClass);
            LOG.debug("\n{}", cu);

            allSources.put(packageDeclaration.appendPackage(typeDefinition.getTypeName()), cu.toString());
        }
        return allSources;
    }

}
